package pl.enigmatic.visual;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JPanel;
import javax.swing.JToggleButton;

import pl.enigmatic.reflection.Accessor;
import pl.enigmatic.reflection.PropertyPath;
import pl.enigmatic.test.Slider;
import pl.enigmatic.time.Timer;
import pl.enigmatic.time.TimerTask;


public abstract class DataVisualizer<X> extends JPanel {

	/** */
	private static final long serialVersionUID = 1L;
	private final X data;
	private final Accessor access;
	private final Object source;
	private final Timer timer = new Timer();
	private final JPanel control = new JPanel();
	private double last = 0;
	private double alpha = 0;

	private DataVisualizer(final X x, final Object source, final Accessor access) {
		this.data = x;
		this.access = access;
		this.source = source;
		final Component vis = createVisualizer();
		final Component set = createSetter();

		final JToggleButton toggle = new JToggleButton("Setter");
		toggle.setEnabled(set != null);

		setLayout(new BorderLayout());
		{
			add(vis, BorderLayout.CENTER);
			final MouseListener mouse = new MouseListener() {

				@Override
				public void mouseReleased(final MouseEvent e) {}

				@Override
				public void mousePressed(final MouseEvent e) {}

				@Override
				public void mouseExited(final MouseEvent e) {}

				@Override
				public void mouseEntered(final MouseEvent e) {}

				@Override
				public void mouseClicked(final MouseEvent e) {
					if (e.getClickCount() > 1) {
						control.setVisible(!control.isVisible());
					}
				}
			};
			vis.addMouseListener(mouse);
			if (toggle.isEnabled()) {
				set.addMouseListener(mouse);
			}
		}
		add(control, BorderLayout.SOUTH);
		control.setVisible(false);
		control.setLayout(new BorderLayout());

		final JPanel north = new JPanel();
		north.setLayout(new GridLayout(1, 3));
		final JCheckBox refresh = new JCheckBox("Refresh");
		refresh.setSelected(true);
		north.add(refresh);

		final JButton btn = new JButton("Refresh");
		btn.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(final ActionEvent e) {
				updateVisualizer();
				final X x = get();
				updateSetter(x);
			}
		});
		north.add(btn);

		toggle.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(final ActionEvent e) {
				if (toggle.isSelected()) {
					remove(vis);
					add(set, BorderLayout.CENTER);
					final X x = get();
					updateSetter(x);
				} else {
					remove(set);
					add(vis, BorderLayout.CENTER);
				}
				validate();
			}
		});
		north.add(toggle);

		final Slider slider = new Slider("Refresh Frequency") {

			/** */
			private static final long serialVersionUID = 1L;

			@Override
			protected void refresh(final double alpha, final double delta, final double factor) {
				DataVisualizer.this.alpha = alpha;
			}
		};
		slider.setValue(1);
		control.add(slider, BorderLayout.CENTER);
		control.add(north, BorderLayout.NORTH);

		timer.schedule(new TimerTask() {

			@Override
			public void run(final double dt) {
				last += dt;
				if (refresh.isSelected() && last >= Timer.DEFAULT_PERIOD * alpha * 60) {
					updateVisualizer();
				}
			}
		});
	}

	public DataVisualizer(final X x) {
		this(x, null, null);
	}

	public DataVisualizer(final Object source, final String property) throws Exception {
		this(null, source, Accessor.createNestedPropertyAccessor(source.getClass(), new PropertyPath(property)));
	}

	@Override
	protected void finalize() throws Throwable {
		timer.cancel();
		super.finalize();
	}

	private final void updateVisualizer() {
		final X x = get();
		updateVisualizer(x);
		last = 0;
	}

	@SuppressWarnings("unchecked")
	protected X get() {
		return (X) (data != null ? data : access.get(source));
	}

	protected void set(final X x) {
		access.set(DataVisualizer.this.source, x);
	}

	protected abstract void updateVisualizer(X data);

	protected abstract void updateSetter(X data);

	protected void update() {};

	protected abstract Component createVisualizer();

	protected abstract Component createSetter();

}
